/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Collections;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  internal abstract class TransformEnumerator<T> : AsyncParentEnumerator<T>
  {
    private static Guid ResolveCountCallId = DataUtils.CreateCallId();
    private static Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static Guid InternalJumpMoveAndFetchCallId = DataUtils.CreateCallId();

    internal TransformEnumerator()
    {
    }

    internal TransformEnumerator( IAsyncEnumerator childEnumerator )
      : base( childEnumerator )
    {
    }

    #region Begin/End Count/GetStatValue Methods

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      CountResult countResult = base.EndCount( asyncResult );

      if( this.AddOrRemoveItems )
      {
        countResult = new CountResult( countResult.Count.ToEstimate() );
      }

      return countResult;
    }

    public override GroupCountResult EndGroupCount( IAsyncResult asyncResult )
    {
      GroupCountResult countResult = base.EndGroupCount( asyncResult );

      if( this.AddOrRemoveItems )
      {
        countResult = new GroupCountResult( countResult.GroupCounts.Select((count) => count.ToEstimate()).ToList() );
      }

      return countResult;
    }

    public override StatResults EndGetStatValues( IAsyncResult asyncResult )
    {
      StatResults statResults = base.EndGetStatValues(asyncResult);

      if(this.AddOrRemoveItems)
      {
        statResults = new StatResults( statResults.Results.Select( ( sr ) => sr.ToEstimate() ).ToList() );
      }

      return statResults;
    }

    #endregion Begin/End Count/GetStatValue Methods

    #region Begin/End JumpMoveAndFetch

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback userCallback,
      object asyncState )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
        TransformEnumerator<T>.JumpMoveAndFetchCallId,
        userCallback,
        asyncState,
        parameters );

      //The move and fetch must sometimes be done in two passes here. The items "moved" over must anyway
      //be fetch in order to test if they pass the filter. So we seperate the operation in two 
      //sub operations: The "move" fetching were we must fetch an amount of items equals to
      //the "move" size, and then the "fetch" fetching were we get the requested fetched items.
      long firstMove;
      int firstFetch;
      Func<AsyncResult, IAsyncResult, MainTransformState<T>, DependentCallResult> firstCallback;

      bool useDirectMoveWithForcedFetch =
        this.AddOrRemoveItems
        && parameters.ApproximateMove
        && ( asyncResult.ExpectedMoveCount != 0 )
        && ( asyncResult.ExpectedFetchCount != 0 )
        && asyncResult.GetMoveUnit() != asyncResult.GetFetchUnit();

      bool useDirectMoveCount =
        !useDirectMoveWithForcedFetch
        && ( ( asyncResult.ExpectedMoveCount == 0 ) || ( parameters.ApproximateMove ) || !this.AddOrRemoveItems );



      if( useDirectMoveWithForcedFetch )
      {
        firstMove = asyncResult.ExpectedMoveCount;
        firstFetch = asyncResult.GetMoveUnit();
        firstCallback = Move_JumpMoveAndFetchCompleted;
      }
      else if( useDirectMoveCount )
      {
        firstMove = asyncResult.ExpectedMoveCount;
        firstFetch = asyncResult.ExpectedFetchCount;

        if( firstFetch == 0 )
        {
          firstFetch = asyncResult.GetMoveUnit();
        }

        firstCallback = Fetch_JumpMoveAndFetchCompleted;
      }
      else
      {
        firstMove = 0;

        firstFetch = Convert.ToInt32( parameters.ExpectedMoveCount + DataUtils.GetUnit( parameters.ExpectedMoveCount ) );

        //Handle the "before first" and "after last" cases.
        if( parameters.SourcePosition.IsBeforeFirst && parameters.ExpectedMoveCount > 0 )
        {
          firstMove = 1;
          firstFetch -= 1;
        }
        else if( parameters.SourcePosition.IsAfterLast && parameters.ExpectedMoveCount < 0 )
        {
          firstMove = -1;
          firstFetch += 1;
        }
        firstCallback = Move_JumpMoveAndFetchCompleted;
      }

      Debug.Assert( firstFetch != 0 );


      JumpMoveAndFetchParameters subParameters = new JumpMoveAndFetchParameters(
                                   parameters.SourcePosition,
                                   firstMove,
                                   firstFetch,
                                   parameters.ApproximateMove );
      asyncResult.Start(
        ( callback, state ) => this.InternalBeginJumpMoveAndFetch(
                                 subParameters,
                                 callback,
                                 state ),
                              ( parentAr, ar ) => firstCallback( parentAr, ar, new MainTransformState<T>() ) );

      return asyncResult;
    }

    private DependentCallResult Move_JumpMoveAndFetchCompleted(
      AsyncResult parentAsyncResult,
      IAsyncResult asyncResult,
      MainTransformState<T> operationState )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = ( JumpMoveAndFetchAsyncResult )parentAsyncResult;
      JumpMoveAndFetchAsyncResult subAsyncResult = ( JumpMoveAndFetchAsyncResult )asyncResult;
      TransformOperationState<T> subResult = TransformEnumerator<T>.InternalEndJumpMoveAndFetch( subAsyncResult );

      Debug.Assert( subAsyncResult.ExpectedFetchCount != 0, "the following code assume such state" );

      operationState.MoveState = subResult;

      SourcePosition fetchSourcePosition = null;

      if( subResult.CumulativeFetch != null && subResult.CumulativeFetch.Count > 0 )
      {
        Debug.Assert( subResult.ActualMoveCount.HasValue && subResult.ActualMoveCount.Value == subAsyncResult.ExpectedMoveCount );

        long moveCount = mainAsyncResult.ExpectedMoveCount;
        int moveUnit = DataUtils.GetUnit( moveCount );

        moveCount -= subAsyncResult.ExpectedMoveCount;

        long fetchDiff = Convert.ToInt64( subResult.CumulativeFetch.Count - 1 ) - Math.Abs( moveCount );

        if( fetchDiff >= 0 )
        {
          T endMoveItem = subResult.CumulativeFetch[ Convert.ToInt32( Math.Abs( moveCount ) ) ];
          fetchSourcePosition = new ItemPosition( endMoveItem );
        }
      }

      if( fetchSourcePosition != null && mainAsyncResult.ExpectedFetchCount != 0 )
      {
        Debug.Assert( fetchSourcePosition != null );

        JumpMoveAndFetchParameters fetchParameters =
          new JumpMoveAndFetchParameters(
            fetchSourcePosition,
            0,
            mainAsyncResult.ExpectedFetchCount,
            false );

        return new DependentCallResult(
                     null,
                     ( callback, state ) => this.InternalBeginJumpMoveAndFetch(
                                              fetchParameters,
                                              callback,
                                              state ),
                     ( parentAr, ar ) => Fetch_JumpMoveAndFetchCompleted( parentAr, ar, operationState ) );
      }
      else
      {
        this.ResolveOperation( operationState, mainAsyncResult );

        return new DependentCallResult( operationState, this.BeginResolveCountValues, this.EndResolveCountValues );
      }
    }

    private DependentCallResult Fetch_JumpMoveAndFetchCompleted(
      AsyncResult parentsAsyncResult,
      IAsyncResult asyncResult,
      MainTransformState<T> operationState )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = ( JumpMoveAndFetchAsyncResult )parentsAsyncResult;
      JumpMoveAndFetchAsyncResult childAsyncResult = ( JumpMoveAndFetchAsyncResult )asyncResult;
      TransformOperationState<T> subResult = TransformEnumerator<T>.InternalEndJumpMoveAndFetch(
                                        ( JumpMoveAndFetchAsyncResult )asyncResult );

      Debug.Assert( childAsyncResult.ExpectedFetchCount != 0, "the following code assume such state" );
      operationState.FetchState = subResult;

      this.ResolveOperation( operationState, mainAsyncResult );

      return new DependentCallResult( operationState, this.BeginResolveCountValues, this.EndResolveCountValues );
    }

    protected virtual IAsyncResult BeginResolveCountValues( AsyncCallback userCallback, object operationStateObject )
    {
      MainTransformState<T> operationState = ( MainTransformState<T> )operationStateObject;
      AsyncResult asyncResult = new AsyncResult( TransformEnumerator<T>.ResolveCountCallId, userCallback, operationStateObject );

      RelativeOffset endPositionOffset = null;
      EstimateInt64 totalCount = null;

      if( operationState.FetchState != null )
      {
        endPositionOffset = operationState.FetchState.LastChildEndPositionOffset;
      }
      else
      {
        endPositionOffset = operationState.MoveState.LastChildEndPositionOffset;
      }

      if( operationState.FetchState != null && operationState.FetchState.TotalCount != null )
      {
        totalCount = operationState.FetchState.TotalCount;
      }
      else if( operationState.MoveState != null && operationState.MoveState.TotalCount != null )
      {
        totalCount = operationState.MoveState.TotalCount;
      }

      if( this.AddOrRemoveItems )
      {
        endPositionOffset = endPositionOffset.ToEstimate();
        totalCount = totalCount.ToEstimate();
      }

      operationState.FinalEndPositionOffset = endPositionOffset;
      operationState.FinalTotalCount = totalCount;

      asyncResult.StartSync( operationState );
      return asyncResult;
    }

    protected virtual DependentCallResult EndResolveCountValues( AsyncResult parentAr, IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( TransformEnumerator<T>.ResolveCountCallId );
      return new DependentCallResult( asyncResult.GetCallResult<MainTransformState<T>>() );
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch(
      JumpMoveAndFetchAsyncResult moveAndFetchAsyncResult )
    {
      moveAndFetchAsyncResult.ValidateCallEnd( TransformEnumerator<T>.JumpMoveAndFetchCallId );

      MainTransformState<T> operationState = moveAndFetchAsyncResult.GetCallResult<MainTransformState<T>>();

      return new JumpMoveAndFetchResult<T>(
        moveAndFetchAsyncResult,
        operationState.FinalActualMove,
        operationState.FinalFetchedItems,
        !operationState.FinalEndPosition.IsOutOfBound,
        operationState.FinalEndPosition,
        operationState.FinalEndPositionOffset,
        operationState.FinalTotalCount );

    }

    private void ResolveOperation( MainTransformState<T> operationState, JumpMoveAndFetchAsyncResult moveAndFetchAsyncResult )
    {
      Nullable<long> actualMoveCount = null;
      List<T> fetchedItems = null;
      SourcePosition endPosition = null;

      TransformOperationState<T> moveState = operationState.MoveState;
      TransformOperationState<T> fetchState = operationState.FetchState;

      if( moveState != null )
      {
        actualMoveCount = moveState.ActualMoveCount;

        if( moveState.CumulativeFetch != null && moveState.CumulativeFetch.Count > 0 )
        {
          long moveCount = moveAndFetchAsyncResult.ExpectedMoveCount;
          int moveUnit = DataUtils.GetUnit( moveCount );


          moveCount -= actualMoveCount.Value;

          long fetchDiff = Convert.ToInt64( moveState.CumulativeFetch.Count - 1 ) - Math.Abs( moveCount );

          if( fetchDiff < 0 )
          {
            endPosition = moveState.LastChildEndPosition;
            Debug.Assert( endPosition.IsOutOfBound );
            actualMoveCount += ( moveState.CumulativeFetch.Count - 1 ) * moveUnit;
            actualMoveCount += moveUnit;
          }
          else
          {
            actualMoveCount += moveCount;
            endPosition = new ItemPosition( moveState.CumulativeFetch[ Convert.ToInt32( Math.Abs( moveCount ) ) ] );
          }
        }
        else
        {
          endPosition = moveState.LastChildEndPosition;
          Debug.Assert( endPosition.IsOutOfBound );
        }
      }

      if( fetchState != null )
      {
        int fetchCount = moveAndFetchAsyncResult.ExpectedFetchCount;
        if( actualMoveCount == null )
        {
          actualMoveCount = fetchState.ActualMoveCount;
        }

        if( fetchState.CumulativeFetch != null && fetchState.CumulativeFetch.Count > 0 )
        {
          if( fetchCount == 0 )
          {
            fetchedItems = new List<T>();
            endPosition = new ItemPosition( fetchState.CumulativeFetch[ 0 ] );
          }
          else if( fetchState.CumulativeFetch.Count >= Math.Abs( fetchCount ) )
          {
            fetchedItems = fetchState.CumulativeFetch.GetRange( 0, Math.Abs( fetchCount ) );
            endPosition = new ItemPosition( fetchedItems[ fetchedItems.Count - 1 ] );
          }
          else
          {
            fetchedItems = new List<T>( fetchState.CumulativeFetch );
            endPosition = fetchState.LastChildEndPosition;
            Debug.Assert( endPosition.IsOutOfBound );
          }
        }
        else
        {
          endPosition = fetchState.LastChildEndPosition;
          Debug.Assert( endPosition.IsOutOfBound );
        }
      }

      if( !endPosition.IsOutOfBound && fetchedItems == null )
      {
        fetchedItems = new List<T>();
      }

      operationState.SetOperationResult( actualMoveCount, fetchedItems, endPosition );
    }

    #endregion Begin/End JumpMoveAndFetch

    #region Internal Begin/End JumpMoveAndFetch

    protected virtual TransformOperationState<T> CreateOperationState( JumpMoveAndFetchParameters parameters )
    {
      TransformOperationState<T> operationState = new TransformOperationState<T>( parameters );
      operationState.NextCallParameters = parameters.CreateWithNewSourcePosition( this.ConvertBackSourcePosition( parameters.SourcePosition ) );
      return operationState;
    }

    private JumpMoveAndFetchAsyncResult InternalBeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback userCallback,
      object asyncState )
    {
      JumpMoveAndFetchAsyncResult asyncResult =
        new JumpMoveAndFetchAsyncResult(
          TransformEnumerator<T>.InternalJumpMoveAndFetchCallId,
          userCallback, asyncState, parameters );

      TransformOperationState<T> operationState = this.CreateOperationState( parameters );

      if( operationState.Completed )
      {
        asyncResult.StartSync( operationState );
        return asyncResult;
      }

      JumpMoveAndFetchParameters convertedParameters = operationState.NextCallParameters;

      if( this.AddOrRemoveItems
        && convertedParameters.ExpectedMoveCount != 0
        && convertedParameters.ExpectedFetchCount != 0
        && DataUtils.GetUnit( convertedParameters.ExpectedMoveCount ) != DataUtils.GetUnit( convertedParameters.ExpectedFetchCount ) )
        throw new DataInternalException();// Move and fetch operations with different directions are not supported.

      asyncResult.Start(
        ( callback, state ) => base.BeginJumpMoveAndFetch(
                                convertedParameters,
                                 callback,
                                 state ),
        ( parentAr, ar ) => InternalJumpMoveAndFetchCompleted( parentAr, ar, operationState ) );

      return asyncResult;
    }

    private DependentCallResult InternalJumpMoveAndFetchCompleted(
      AsyncResult parentAsyncResult, IAsyncResult childAsyncResult,
      TransformOperationState<T> operationState )
    {
      JumpMoveAndFetchResult childResult = this.ChildEnumerator.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )childAsyncResult );
      JumpMoveAndFetchAsyncResult internAsyncResult = ( JumpMoveAndFetchAsyncResult )parentAsyncResult;

      if( m_disposed )
        return new DependentCallResult( null );

      DependentCallResult dependentCallResult = null;

      this.MergeChildResult( operationState, childResult );

      if( !operationState.Completed )
      {

        dependentCallResult = new DependentCallResult(
          null,
          ( callback, state ) => base.BeginJumpMoveAndFetch(
                                   operationState.NextCallParameters,
                                   callback,
                                   state ),
          ( parentAr, ar ) => InternalJumpMoveAndFetchCompleted( parentAr, ar, operationState ) );
      }
      else
      {
        dependentCallResult = new DependentCallResult( operationState );
      }

      return dependentCallResult;
    }

    private static TransformOperationState<T> InternalEndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( TransformEnumerator<T>.InternalJumpMoveAndFetchCallId );

      TransformOperationState<T> operationState = asyncResult.GetCallResult<TransformOperationState<T>>();

      return operationState;
    }
    #endregion Internal Begin/End JumpMoveAndFetch

    protected virtual SourcePosition ConvertBackSourcePosition( SourcePosition sourcePosition )
    {
      if( sourcePosition is ItemPosition )
      {
        T item = ( T )( ( ItemPosition )sourcePosition ).Item;
        object rawItem;
        if( !this.TryConvertBack( item, out rawItem ) )
          throw new InvalidOperationException(
            "An implementation of TransformEnumerator should always be able to convert back items specified in ItemPositions"
        + " or override the ConvertBackSourcePosition method" );

        return new ItemPosition( rawItem );
      }
      return sourcePosition;
    }

    protected virtual void MergeChildResult( TransformOperationState<T> operationState, JumpMoveAndFetchResult childResult )
    {
      bool addOrRemoveItems = this.AddOrRemoveItems;
      Nullable<JumpMoveAndFetchParameters> convertedParameters = null;

      if( !operationState.FirstFetchCompleted )
      {
        //It's our first pass here. we can use the initial parameters
        //as convertedParameters for the operation
        convertedParameters = operationState.InitialParameters;
        operationState.SetFirstFetchCompleted( childResult.ActualMoveCount );
      }

      IEnumerable<T> insertionItems;
      object rawPreviousItem = null;
      object rawCurrentItem = null;
      T convertedPreviousItem = default( T );
      T convertedCurrentItem = default( T );
      bool reverseDirection = ( childResult.GetFetchUnit() < 0 );
      bool keepItem = false;

      List<object> rawFetchedItems;

      if( childResult.FetchedObjects != null )
      {
        operationState.AddCumulativeFetch( new T[ 0 ] );
        rawFetchedItems = childResult.FetchedObjects.Cast<object>().ToList();
      }
      else
      {
        rawFetchedItems = new List<object>();
      }

      //End pass the end in order to handle the end of source insertions
      for( int i = 0; i <= rawFetchedItems.Count; i++ )
      {
        if( i < rawFetchedItems.Count )
        {
          rawCurrentItem = rawFetchedItems[ i ];
          keepItem = this.TryConvert( rawCurrentItem, out convertedCurrentItem );
        }
        else
        {
          rawCurrentItem = null;
          convertedCurrentItem = default( T );
          keepItem = false;
        }

        if( addOrRemoveItems )
        {
          if( i == 0 )
          {
            insertionItems = this.GetInsertedItems(
              convertedParameters, childResult.GetParameters(),
                rawCurrentItem, convertedCurrentItem );
          }
          else if( i == rawFetchedItems.Count )
          {
            insertionItems = null;
            if( !childResult.Result )
            {
              insertionItems = this.GetInsertedItems( reverseDirection, rawPreviousItem, convertedPreviousItem );
            }
          }
          else
          {
            insertionItems = this.GetInsertedItems( reverseDirection,
              rawPreviousItem, convertedPreviousItem,
              rawCurrentItem, convertedCurrentItem );
          }

          operationState.AddCumulativeFetch( insertionItems );
        }
        else
        {
          if( !keepItem && i < rawFetchedItems.Count )
            throw new InvalidOperationException(
              "An implementation of TransformEnumerator should always be able to convert items if AddOrRemoveItems property return false."
              );
        }

        if( keepItem )
        {
          operationState.AddCumulativeFetch( new T[] { convertedCurrentItem } );
        }

        rawPreviousItem = rawCurrentItem;
        convertedPreviousItem = convertedCurrentItem;
      }

      operationState.LastChildResult = childResult;

      if( childResult.TotalCount != null )
      {
        operationState.TotalCount = childResult.TotalCount;
      }

      int nextFetchSize =
        ( operationState.RemainingFetchCount == 0 )
        ? childResult.GetFetchUnit()
        : operationState.RemainingFetchCount;

      operationState.NextCallParameters = new JumpMoveAndFetchParameters(
        childResult.EndPositionObject,
        childResult.GetFetchUnit(),
        nextFetchSize );
    }

    protected virtual IEnumerable<T> GetInsertedItems(
      Nullable<JumpMoveAndFetchParameters> convertedParameters,
      JumpMoveAndFetchParameters rawParameters,
      object rawFirstFetchedItem, T convertedFirstFetchedItem )
    {
      IEnumerable<T> insertedHeaders = null;

      long rawMoveCount = rawParameters.ExpectedMoveCount;
      SourcePosition rawSourcePosition = rawParameters.SourcePosition;
      bool reverseDirection = ( rawParameters.GetFetchUnit() == -1 );

      //This default implementation does not return values for move and fetch in opposite directions
      if( ( DataUtils.GetUnit( rawMoveCount ) == -1 ) ^ reverseDirection )
        return null;

      if( Math.Abs( rawMoveCount ) == 1 && rawSourcePosition is ItemPosition )
      {
        object rawPreviousItem = ( ( ItemPosition )rawSourcePosition ).Item;
        T convertedPreviousItem;

        convertedPreviousItem = this.ConvertOrDefault( rawPreviousItem );
        insertedHeaders = this.GetInsertedItems( reverseDirection, rawPreviousItem, convertedPreviousItem, rawFirstFetchedItem, convertedFirstFetchedItem );
      }
      else if( rawMoveCount == 1 && rawSourcePosition.IsBeforeFirst && !reverseDirection )
      {
        insertedHeaders = this.GetInsertedItems( false, null, default( T ), rawFirstFetchedItem, convertedFirstFetchedItem );
      }
      else if( rawMoveCount == -1 && rawSourcePosition.IsAfterLast && reverseDirection )
      {
        insertedHeaders = this.GetInsertedItems( true, null, default( T ), rawFirstFetchedItem, convertedFirstFetchedItem );
      }

      return insertedHeaders;
    }

    protected virtual IEnumerable<T> GetInsertedItems( bool reverseDirection, object rawLastSourceItem, T convertedLastSourceItem )
    {
      return this.GetInsertedItems( reverseDirection, rawLastSourceItem, convertedLastSourceItem, null, default( T ) );
    }

    private T ConvertOrDefault( object rawItem )
    {
      T convertedItem;
      if( this.TryConvert( rawItem, out convertedItem ) )
      {
        return convertedItem;
      }
      return default( T );
    }

    #region Abstract interface
    protected abstract bool AddOrRemoveItems
    {
      get;
    }

    protected abstract IEnumerable<T> GetInsertedItems( bool reverseDirection, object rawPreviousItem, T convertedPreviousItem, object rawNextItem, T convertedNextItem );

    protected abstract bool TryConvertBack( T convertedItem, out object rawItem );

    protected abstract bool TryConvert( object rawItem, out T convertedItem );

    #endregion Abstract interface

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;
      base.Dispose( disposing );
    }

    #region Private Fields

    private bool m_disposed; //false

    #endregion Private Fields

  }
}
